/**
 * @file rf_spi.c
 * @brief Implementation of spi functions used in the RF24 library
 * @author Jeff Eaton
 */

#include "rf_spi.h"

// static stuff internal to the SPI library
static volatile uint16_t* SS_LAT_REG;
static uint16_t SS_pin_mask;


void spi_init(const SPI_MODE mode, volatile uint16_t* m_SS_LAT_REG, const uint8_t SSpin) {

    SS_LAT_REG = m_SS_LAT_REG;
    SS_pin_mask = (1<<SSpin);

    SPI1CON1bits.MODE16 = (mode==SPI_8BIT ? 0 : 1);
    SPI1CON1bits.MSTEN = 1;     // master mode
    SPI1CON1bits.SMP = 0;       // sample input in the middle of output
    SPI1CON1bits.CKE = 1;
    SPI1CON1bits.CKP = 0;

    SPI1CON1bits.PPRE = 0b10;   // 4:1 primary prescaling   (40->10)
    SPI1CON1bits.SPRE = 0b110;  // 2:1 secondary prescaling (10->5)

    // if you wanna use interrupts, here's the config for it
    IPC2bits.SPI1IP = 4;        // average priority
    IFS0bits.SPI1IF = 0;        // clear interrupt flag
    IEC0bits.SPI1IE = 0;        // disable interrupt

    SPI1STATbits.SPIROV = 0;    // clear receive overflow flag
    SPI1STATbits.SPIEN = 1;     // enable SPI 1

    spi_ss(true);
}

void spi_ss(const bool state) {
    if(state) { // drive slave select high
        *SS_LAT_REG |= SS_pin_mask;
    }
    else {
        *SS_LAT_REG &= ~SS_pin_mask;
    }
}

uint8_t spi_write8(const uint8_t byte) {
    uint8_t readValue;

    spi_ss(false);

    SPI1BUF = byte;
    while(!SPI1STATbits.SPIRBF);
    // the SPI rx buffer will now be filled with the status byte
    readValue = SPI1BUF;

    spi_ss(true);

    return readValue;
}

uint16_t spi_write16(const uint16_t word) {
    uint16_t readValue;

    spi_ss(false);

    SPI1BUF = word;
    while(!SPI1STATbits.SPIRBF);
    readValue = SPI1BUF;

    spi_ss(true);
    
    return readValue;
}

uint8_t spi_write(const uint8_t command, const uint8_t* buf, uint8_t len) {
    uint8_t status;
    uint16_t dummy;
    
    spi_ss(false);

    // write the command byte
    SPI1BUF = command;
    while(!SPI1STATbits.SPIRBF);
    status = SPI1BUF;

    // write len bytes of data stored in buf
    while(len--) {
        SPI1BUF = *buf++;
        while(!SPI1STATbits.SPIRBF);
        dummy = SPI1BUF;
    }

    spi_ss(true);

    return status;
}

uint8_t spi_read(const uint8_t command, const uint8_t nopChar, uint8_t* buf, uint8_t len) {
    uint8_t status;

    spi_ss(false);

    // write the command byte
    SPI1BUF = command;
    while(!SPI1STATbits.SPIRBF);
    status = SPI1BUF;

    // write nopChar out len times and store incoming data in buf
    while(len--) {
        SPI1BUF = nopChar;
        while(!SPI1STATbits.SPIRBF);
        *buf++ = SPI1BUF;
    }

    spi_ss(true);

    return status;
}

uint8_t spi_read_no_ss(const uint8_t command, const uint8_t nopChar, uint8_t* buf, uint8_t len) {
    uint8_t status;

    // write the command byte
    SPI1BUF = command;
    while(!SPI1STATbits.SPIRBF);
    status = SPI1BUF;

    // write nopChar out len times and store incoming data in buf
    while(len--) {
        SPI1BUF = nopChar;
        while(!SPI1STATbits.SPIRBF);
        *buf++ = SPI1BUF;
    }

    return status;
}

uint8_t spi_write8_no_ss(const uint8_t byte) {
    uint8_t readValue;

    SPI1BUF = byte;
    while(!SPI1STATbits.SPIRBF);
    readValue = SPI1BUF;

    return readValue;
}

uint8_t spi_write_no_ss(const uint8_t* buf, uint8_t len) {
    uint8_t readValue;

    while(len--) {
        SPI1BUF = *buf++;
        while(!SPI1STATbits.SPIRBF);
        readValue = SPI1BUF;
    }

    return readValue;
}